-- -------------------------------------------------------------------------
-- Lib/lang.lua (Localization System)
-- Pure English-key format: {"English text": "Translation"}
-- Scans languages directory for available language files
-- -------------------------------------------------------------------------

local lang = {}

-- [Lua 5.4] <const> for function references
local type <const> = type
local pairs <const> = pairs
local ipairs <const> = ipairs
local pcall <const> = pcall
local tostring <const> = tostring

-- Dependencies (use local json library)
local json_lib = require("ClickUl.Lib.json")
local features_config = nil  -- Will be set in init()
local debugger = nil

-- State
local current_lang = "en"
local translation_data = {}
local translation_cache = {}
local cache_enabled = true
local available_langs = {}
local current_lang_idx = 1

-- Paths
local SCRIPT_PATH = (paths and paths.script) or ".\\"
if type(SCRIPT_PATH) ~= "string" or SCRIPT_PATH == "" then SCRIPT_PATH = ".\\" end
if not SCRIPT_PATH:match("[\\/]$") then SCRIPT_PATH = SCRIPT_PATH .. "\\" end
local LOCALES_DIR = SCRIPT_PATH .. "ClickUl\\languages\\"

-- -------------------------------------------------------------------------
-- Utility Helpers
-- -------------------------------------------------------------------------

local function safe_close(f)
    if not f then return end
    if type(f.close) == "function" then pcall(f.close, f) end
    if type(f.Close) == "function" then pcall(f.Close, f) end
end

local function file_exists(path)
    if type(path) ~= "string" or path == "" then return false end

    if file and file.exists then
        local ok, exists = pcall(file.exists, path)
        if ok then return exists == true end
    end
    return false
end

local function safe_read_file(path)
    if type(path) ~= "string" or path == "" then return nil end

    if file and file.open and file.exists then
        local ok, content = pcall(function()
            if not file.exists(path) then return nil end
            local f = file.open(path, { create_if_not_exists = false, append = false })
            if f and f.valid then
                local text = f.text
                safe_close(f)
                return text
            end
            safe_close(f)
            return nil
        end)

        if ok and type(content) == "string" then
            return content
        end
    end
    return nil
end

local function safe_json_decode(str)
    if type(str) ~= "string" then return nil end

    local ok, result = pcall(json_lib.decode, str)
    if ok and result then return result end

    return nil
end

local function validate_translation_data(data)
    if type(data) ~= "table" then return {} end

    local validated = {}
    for k, v in pairs(data) do
        if type(k) == "string" and type(v) == "string" then
            validated[k] = v
        end
    end

    return validated
end

local function clear_cache()
    translation_cache = {}
    if debugger and debugger.debug then
        debugger.debug("Translation cache cleared")
    end
end

-- -------------------------------------------------------------------------
-- Directory Scanning
-- -------------------------------------------------------------------------

--- Scan languages directory and return list of available language codes
-- @return table Array of language codes
local function scan_languages_directory()
    local languages = {}
    local found = {}

    -- Use Lexis dirs.list(path, ext) -> string[]
    if dirs and dirs.list then
        local ok, result = pcall(dirs.list, LOCALES_DIR, "json")
        if ok and result then
            for i = 1, #result do
                local path = result[i]
                -- Extract filename from path (e.g., "en.json" from full path)
                local filename = path:match("([^/\\]+)$") or path
                -- Remove .json extension
                local name = filename:gsub("%.json$", "")
                if name ~= "" and name ~= "template" and not found[name] then
                    languages[#languages + 1] = name
                    found[name] = true
                end
            end
        end
    end

    -- Always expose English as an option even if no en.json exists.
    -- (lang.load("en") supports file-less fallback.)
    if not found["en"] then
        languages[#languages + 1] = "en"
        found["en"] = true
    end

    -- Sort: "en" first, then alphabetically
    table.sort(languages, function(a, b)
        if a == "en" then return true end
        if b == "en" then return false end
        return a < b
    end)

    return languages
end

-- -------------------------------------------------------------------------
-- Public API
-- -------------------------------------------------------------------------

--- Initialize language system.
-- @param cfg table Features config module reference (expects cfg.get_language() and cfg.set_language())
-- @param _ unused (previously json_lib, now using native json)
-- @param debug_lib table Debugger library (optional)
function lang.init(cfg, _, debug_lib)
    features_config = cfg
    debugger = debug_lib

    -- Create languages directory if possible
    if dirs and dirs.create then
        pcall(dirs.create, LOCALES_DIR)
    end

    -- Refresh available languages first
    lang.refresh_available()

    -- Determine initial language
    local initial = nil

    -- 1. Try features config setting
    if features_config and features_config.get_language then
        initial = features_config.get_language()
    end

    -- 2. If config language not available, use first available language
    if not initial or not lang.is_available(initial) then
        if #available_langs > 0 then
            initial = available_langs[1]
        else
            initial = "en"  -- Fallback to English if no language files found
        end
    end

    lang.load(initial)

    if debugger and debugger.info then
        debugger.info("Language system initialized", {
            current = current_lang,
            locales_dir = LOCALES_DIR,
            available = #available_langs,
            json_available = json_lib ~= nil
        })
    end

    return true
end

--- Check if a language is available
-- @param lang_code string
-- @return boolean
function lang.is_available(lang_code)
    if type(lang_code) ~= "string" then return false end
    for _, code in ipairs(available_langs) do
        if code == lang_code then return true end
    end
    return false
end

--- Load language file.
-- @param lang_code string
-- @return boolean
function lang.load(lang_code)
    if type(lang_code) ~= "string" or lang_code == "" then
        if debugger and debugger.warn then
            debugger.warn("Invalid language code", { code = lang_code })
        end
        return false
    end

    -- For "en" without file, use empty translations (keys = values)
    local file_path = LOCALES_DIR .. lang_code .. ".json"

    if not file_exists(file_path) then
        if lang_code == "en" then
            -- English fallback: no translations needed
            current_lang = "en"
            translation_data = {}
            clear_cache()

            -- Sync current language index
            for i, code in ipairs(available_langs) do
                if code == "en" then
                    current_lang_idx = i
                    break
                end
            end

            return true
        end

        if debugger and debugger.warn then
            debugger.warn("Language file not found", { code = lang_code, file_path = file_path })
        end
        return false
    end

    if not json_lib then
        if debugger and debugger.warn then
            debugger.warn("Cannot load language file: JSON library not available", { code = lang_code })
        end
        return false
    end

    local content = safe_read_file(file_path)
    if not content or #content < 2 then
        if debugger and debugger.warn then
            debugger.warn("Language file empty", { code = lang_code, file_path = file_path })
        end
        return false
    end

    local decoded = safe_json_decode(content)
    if not decoded then
        if debugger and debugger.error then
            debugger.error("Failed to decode language file", { code = lang_code, file_path = file_path })
        end
        return false
    end

    translation_data = validate_translation_data(decoded)
    current_lang = lang_code
    clear_cache()

    -- Sync current language index
    for i, code in ipairs(available_langs) do
        if code == lang_code then
            current_lang_idx = i
            break
        end
    end

    if debugger and debugger.info then
        local count = 0
        for _ in pairs(translation_data) do count = count + 1 end
        debugger.info("Language loaded", { code = lang_code, translations = count, index = current_lang_idx })
    end

    return true
end

--- Reload current language.
function lang.reload()
    local code = current_lang
    translation_data = {}
    clear_cache()
    return lang.load(code)
end

--- Translate an English key.
-- @param key string
-- @return string
function lang.get(key)
    if key == nil then return "" end

    if type(key) ~= "string" then
        return tostring(key)
    end

    if cache_enabled then
        local cached = translation_cache[key]
        if cached ~= nil then return cached end
    end

    -- If English or no translation, return key as-is.
    -- Treat empty-string translations as "not translated yet" and fall back.
    local val = translation_data[key]
    if val ~= nil and val ~= "" then
        if cache_enabled then translation_cache[key] = val end
        return val
    end

    if cache_enabled then translation_cache[key] = key end
    return key
end

lang.get_text = lang.get

--- Change language, save to features config.
function lang.set_language(lang_code)
    if not lang.load(lang_code) then
        return false
    end

    if features_config and features_config.set_language then
        features_config.set_language(lang_code)
        if features_config.save then
            local ok, err = pcall(features_config.save)
            if not ok and debugger and debugger.warn then
                debugger.warn("Language changed but failed to save config", { error = tostring(err) })
            end
        end
    end

    return true
end

function lang.current()
    return current_lang
end

function lang.set_cache_enabled(enabled)
    cache_enabled = enabled == true
    if not cache_enabled then
        clear_cache()
    end
end

--- List available language codes (cached, scanned once at init).
function lang.get_available_languages()
    return available_langs
end

--- Refresh cached available language list + current index.
function lang.refresh_available()
    available_langs = scan_languages_directory()

    -- Find current language index
    current_lang_idx = 1
    for i, code in ipairs(available_langs) do
        if code == current_lang then
            current_lang_idx = i
            break
        end
    end

    return available_langs
end

function lang.get_lang_list()
    return available_langs
end

function lang.get_lang_index()
    return current_lang_idx
end

function lang.set_lang_by_index(idx)
    if type(idx) ~= "number" or idx < 1 or idx > #available_langs then
        return false
    end

    local code = available_langs[idx]
    if not code then return false end

    if not lang.set_language(code) then
        return false
    end

    current_lang_idx = idx
    return true
end

function lang.get_stats()
    local translation_count = 0
    local cache_count = 0

    for _ in pairs(translation_data) do translation_count = translation_count + 1 end
    for _ in pairs(translation_cache) do cache_count = cache_count + 1 end

    return {
        current_language = current_lang,
        translations_loaded = translation_count,
        cache_entries = cache_count,
        cache_enabled = cache_enabled,
        available_languages = #available_langs,
        json_available = json_lib ~= nil,
        locales_dir = LOCALES_DIR
    }
end

--- Get the languages directory path
function lang.get_locales_dir()
    return LOCALES_DIR
end

return lang
